package com.seleniti.enpareja.web.appbase.client.control.controller;

import org.ioc.commons.flowcontrol.exceptionparser.ExceptionParserManagerImpl;
import org.ioc.commons.japtsy.shared.model.biz.JAPTSYServiceException;
import org.ioc.commons.japtsy.shared.model.biz.JAPTSYServiceException.ErrorCode;
import org.ioc.commons.utils.Format;
import org.ioc.commons.utils.FormatterLogger;

import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.StatusCodeException;
import com.seleniti.enpareja.controller.common.domain.LoggedUserInfo;
import com.seleniti.enpareja.controller.common.exception.EnParejaException;
import com.seleniti.enpareja.controller.control.factory.ClientFactory;
import com.seleniti.enpareja.controller.control.factory.EnParejaExceptionController;
import com.seleniti.enpareja.controller.control.place.EnParejaPlace;

public class EnParejaExceptionControllerImpl implements EnParejaExceptionController {
	private static FormatterLogger logger = FormatterLogger.getLogger(EnParejaExceptionControllerImpl.class);
	private static EnParejaExceptionControllerImpl singleton;
	private ClientFactory clientFactory;

	private final UncaughtExceptionHandler handler = new UncaughtExceptionHandler() {

		public void onUncaughtException(Throwable e) {
			EnParejaExceptionControllerImpl.this.handleException(e, null);
		}
	};

	public static EnParejaExceptionControllerImpl get() {
		if (singleton == null) {
			singleton = new EnParejaExceptionControllerImpl();
		}

		return singleton;
	}

	private final ExceptionParserManagerImpl<EnParejaException> exceptionParser = new ExceptionParserManagerImpl<EnParejaException>();

	private EnParejaExceptionControllerImpl() {
		// Singleton
	}

	@SuppressWarnings("incomplete-switch")
	@Override
	public void handleException(final Throwable e, String msgInfo, Object... params) {

		String userEmail = "(desconocido, no logado)";
		if (clientFactory != null) {
			LoggedUserInfo lui = (LoggedUserInfo) clientFactory.getLoginController().getLoggedUserInfo();
			userEmail = lui == null ? userEmail : lui.getUserEmail();
		}

		if (e instanceof IncompatibleRemoteServiceException && clientFactory != null) {
			logger.warn(e, "Parece que el usuario {0} tiene cargada una versión desactualizada.", userEmail);
			clientFactory.getPlaceController().forThisObject(this).goTo(EnParejaPlace.RequiredReload);
		} else if (e instanceof StatusCodeException && ((StatusCodeException) e).getStatusCode() == 0) {
			logger.warn(e, "Recarga de página hecha antes de finalizar petición (usuario {0})", userEmail);
		} else {

			/**
			 * Logamos la excepción. Este log sólo aparece a la hora de depurar.
			 * Una vez compilada la aplicación no es logada.
			 */

			String msg = Format.substitute("Exception thrown for user {0}", userEmail);
			if (msgInfo != null && !msgInfo.isEmpty()) {
				msg = Format.substitute("Exception thrown for user {0}: {1}", userEmail,
						Format.substitute(msgInfo, params));
			} else if (e.getMessage() != null && !e.getMessage().isEmpty()) {
				msg += ": " + e.getMessage();
			}

			logger.error(e, msg);

			Throwable t = e;
			do {
				if (t instanceof JAPTSYServiceException) {
					ErrorCode code = ((JAPTSYServiceException) t).getErrorCode();

					switch (code) {
					case INVALID_DOMAIN_CLASS:
						logger.warn(t, "Parece que el usuario {0} tiene cargada una versión desactualizada.", userEmail);
						clientFactory.getPlaceController().forThisObject(this).goTo(EnParejaPlace.RequiredReload);

						return;
					case SERVICE_ERROR:
						EnParejaException epe = (t instanceof EnParejaException ? (EnParejaException) t : fromMessage(
								EnParejaException.class, ((JAPTSYServiceException) t).getDetailedInfo()));

						if (epe != null) {
							EnParejaException.ErrorCode epCode = epe.getErrorCode();

							if (epCode != null && clientFactory != null) {
								switch (epCode) {
								case LOGGIN_REQUIRED:
									clientFactory.getLoginController().refreshLoggedUserInfo();
									clientFactory
											.getMessageBoxFactory()
											.forThisObject(this)
											.info("Sesión caducada",
													"Demasiado tiempo sin usar la aplicación. Inténtalo de nuevo.");
									return;
								}
							}

						}
						break;
					case UNABLE_TO_GET_ENTITY_BY_ID:
						if (clientFactory != null) {
							clientFactory
									.getMessageBoxFactory()
									.forThisObject(this)
									.info("Elemento eliminado",
											"Parece que ese elemento ya no existe. ¿Lo eliminó tu pareja?");
							clientFactory.getShell().refreshNow();
						}
						return;

					}

				}

			} while ((t = t.getCause()) != null);

			Window.alert("Oh oh se produjo un error inesperado. Prueba a recargar la página dentro de un rato e intenta de nuevo.");
		}
	}

	public UncaughtExceptionHandler getHandler() {
		return handler;
	}

	public <EX extends EnParejaException> void registerExceptionParser(Class<EX> clazz,
			org.ioc.commons.flowcontrol.exceptionparser.ExceptionParserManager.ExceptionParser<EX> parser) {
		exceptionParser.registerExceptionParser(clazz, parser);
	}

	public <EX extends EnParejaException> EX fromMessage(Class<EX> clazz, String message) {
		return exceptionParser.fromMessage(clazz, message);
	}

	@Override
	public <EX extends EnParejaException> void unregisterExceptionParser(Class<EX> exceptionClass) {
		exceptionParser.unregisterExceptionParser(exceptionClass);
	}

	public ClientFactory getClientFactory() {
		return clientFactory;
	}

	public void setClientFactory(ClientFactory clientFactory) {
		this.clientFactory = clientFactory;
	}
}
